package com.mes.cloud.security;

import com.alibaba.fastjson.JSON;
import com.mes.cloud.base.ErrorNotifier;
import com.mes.cloud.base.ExceptionContext;
import com.mes.cloud.commons.Constants;
import com.mes.cloud.utils.JSONResult;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.util.HTTPRequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;

/**
 * Created by Lenovo on 2017/9/18.
 */
@Component
public class ZuulListeners {

    private static final Logger LOGGER = LoggerFactory.getLogger(ZuulListeners.class);

    @Autowired
    private ErrorNotifier errorNotifier;

    @Autowired(required = false)
    private List<ZuulListener> listenerList = new ArrayList<ZuulListener>();

    private Map<String, ZuulListener> path2Listeners = new HashMap<String, ZuulListener>();

    private ConcurrentMap<String, List<ZuulListener>> uri2Listeners = new ConcurrentHashMap<>();

    static PathMatcher PATH_MATCHER = new AntPathMatcher();

    @PostConstruct
    public void init() {
        listenerList.forEach(listener -> {
            Path path = AnnotationUtils.findAnnotation(listener.getClass(), Path.class);
            if (path != null && path.value() != null && path.value().length > 0) {
                Stream.of(path.value()).filter(p -> StringUtils.hasText(p))
                        .forEach(p -> path2Listeners.put(p, listener));
                ;
            }
        });
    }

    private List<ZuulListener> getListenersByUri(String uri) {
        if (uri2Listeners.containsKey(uri)) {
            return uri2Listeners.get(uri);
        }

        List<ZuulListener> listener = path2Listeners.keySet().stream()
                .filter(path -> PATH_MATCHER.match(path, uri))
                .map(key -> path2Listeners.get(key)).collect(Collectors.toList());

        uri2Listeners.put(uri, listener);
        return listener;
    }

    public void beforeRequest() {
        RequestContext context = RequestContext.getCurrentContext();
        // 获取的是url后面的参数
        Map<String, List<String>> params = HTTPRequestUtils.getInstance().getQueryParams();
        if (params == null) {
            params = new HashMap<>();
            RequestContext.getCurrentContext().setRequestQueryParams(params);
        }
        // 不带hostname的url路径
        String uri = context.getRequest().getRequestURI();
        getListenersByUri(uri).forEach(listener -> listener.beforeRequest(context));
    }

    private void logError(RequestContext context, Throwable e) {
        LOGGER.error(e.getMessage(), e);
        errorNotifier.commonNotify(new ExceptionContext(context.getRequest(), context.getResponse(), e));
    }

    //TODO:gzip的解和压对性能有一定的影响
    public void afterResponse() {
        RequestContext context = RequestContext.getCurrentContext();

        String uri = context.getRequest().getRequestURI();
        List<ZuulListener> listeners = getListenersByUri(uri).stream()
                .filter(listener -> listener.wantToParseResponse(context)).collect(Collectors.toList());
        if (listeners.isEmpty()) return;

        String responseBody = context.getResponseBody();
        InputStream is = context.getResponseDataStream();
        // 判断返回的是不是流数据
        if (responseBody == null && is != null) {
            try {
                if (context.getResponseGZipped()) {
                    Long len = context.getOriginContentLength();
                    if (len == null || len > 0) {
                        is = new GZIPInputStream(is);
                    }
                }
                responseBody = StreamUtils.copyToString(is, Constants.UTF8);
            } catch (IOException e) {
                logError(context, e);
            } finally {
                try {
                    is.close();
                } catch (IOException e) {
                    LOGGER.error("HttpClient Connection Leak.", e);
                }
            }
        }

        JSONResult result = Constants.Errors.SERVER_ERROR;

        if (StringUtils.hasText(responseBody)) {
            try {
                result = JSON.parseObject(responseBody, JSONResult.class);
            } catch (Throwable e) {
                logError(context, e);
            }
        }

        for (ZuulListener listener : listeners) {
            try {
                result = listener.afterResponse(result);
            } catch (Throwable e) {
                logError(context, e);
                break;
            }
        }

        if (result != null) {
            responseBody = JSON.toJSONString(result);
        }

        context.setResponseBody(responseBody);
    }
}
